<template>
	<div class="v-select" @touchmove.stop.prevent>
		<slot :open="open" :label="activeLabel">
			<input :value="activeLabel" type="text" disabled @click="open" :placeholder="placeholder" />
		</slot>

		<v-popup ref="vPopupRef" :closeable="false" round :round-size="40" :duration="500">
			<div class="header">
				<div class="cancel" @click="onCancel">取消</div>
				<div class="title">{{ placeholder }}</div>
				<div class="confirm" @click="onConfirm">确定</div>
			</div>
			<div class="column-wrap">
				<div
					class="column"
					v-for="(column, index) in column"
					:key="index"
					v-if="column.show"
					@touchstart="onTouchStart($event, index)"
					@touchmove="onTouchMove($event, index)"
					@touchend="onTouchEnd($event, index)"
				>
					<div
						class="wrap"
						:class="[column.columnStatus]"
						:style="[
							{
								transform: `translate3d(0, ${column.initTop}px, 0)`,
							},
						]"
					>
						<div class="item" v-for="(item, index) in column.list" :key="index">{{ item[labelKey] }}</div>
					</div>
					<div class="frame"></div>
					<div class="mask"></div>
				</div>
			</div>
			<div style="height: 30rpx"></div>
		</v-popup>
	</div>
</template>

<script>
const initTop = uni.upx2px(250) - uni.upx2px(40)
const itemHeight = uni.upx2px(80)

export default {
	name: "v-select",
	model: {
		prop: "value",
		event: "update:value",
	},
	props: {
		value: {
			type: [String, Number, Array],
			default: "",
		},
		disabled: {
			type: Boolean,
			default: false,
		},
		labelKey: {
			type: String,
			default: "label",
		},
		valueKey: {
			type: String,
			default: "value",
		},
		list: {
			type: Array,
			default: () => [],
		},
		placeholder: {
			type: String,
			default: "请选择",
		},
	},
	data() {
		return {
			column: [
				{
					list: [],
					startY: 0,
					startTop: 0,
					moveY: 0,
					moveTop: 0,
					activeIndex: 0,
					columnStatus: "",
					show: true,
					initTop: uni.upx2px(250) - uni.upx2px(40),
				},
				{
					list: [],
					startY: 0,
					startTop: 0,
					moveY: 0,
					moveTop: 0,
					activeIndex: 0,
					columnStatus: "",
					show: false,
					initTop: uni.upx2px(250) - uni.upx2px(40),
				},
				{
					list: [],
					startY: 0,
					startTop: 0,
					moveY: 0,
					moveTop: 0,
					activeIndex: 0,
					columnStatus: "",
					show: false,
					initTop: uni.upx2px(250) - uni.upx2px(40),
				},
			],
			activeLabel: "",
		}
	},
	computed: {
		wrapHeight() {
			return this.list.length * itemHeight
		},
	},
	methods: {
		open() {
			this.onFormatColumn()
			this.$refs.vPopupRef.show()
		},
		onFormatColumn() {
			if (this.value) {
				if (Array.isArray(this.value)) {
					this.column.forEach((item, index) => {
						switch (index) {
							case 0:
								item.list = this.list
								break
							case 1:
								item.list = this.column[0].list[this.column[0].activeIndex].children || []
								break
							case 2:
								item.list = this.column[1].list[this.column[1].activeIndex].children || []
								break
						}

						let i = item.list.findIndex(i => i[this.valueKey] == this.value[index])
						if (i < 0) i = 0
						item.initTop = initTop - i * itemHeight
						item.activeIndex = i
					})
				} else {
					let index = this.column[0].list.findIndex(i => i[this.valueKey] == this.value)
					if (index < 0) index = 0
					this.column[0].initTop = initTop - index * itemHeight
				}
				this.onFormatLable()
			}
		},
		onFormatLable() {
			if (Array.isArray(this.value)) {
				this.activeLabel = this.value
					.map((item, index) => {
						const data = this.column[index].list.find(i => i[this.valueKey] == item)
						return data ? data[this.labelKey] : ""
					})
					.join("/")
			} else {
				const data = this.column[0].list.find(i => i[this.valueKey] == this.value)
				this.activeLabel = data ? data[this.labelKey] : ""
			}
		},

		onCancel() {
			this.startTop = 0
			this.moveTop = 0
			this.column.forEach(item => {
				item.initTop = initTop
			})

			this.$refs.vPopupRef.hide()
		},
		onConfirm() {
			this.onFormatLable()
			if (Array.isArray(this.value)) {
				const value = this.column.map(item => (item.show ? item.list[item.activeIndex][this.valueKey] : null))
				this.$emit(
					"update:value",
					value.filter(item => item !== null)
				)
				this.$emit("change", value)
			} else {
				this.$emit("update:value", this.column[0].list[item.activeIndex][this.valueKey])
				this.$emit("change", this.column[0].list[item.activeIndex])
			}
			this.onCancel()
		},

		onTouchStart(e, index) {
			this.column[index].startY = e.touches[0].pageY
			this.column[index].startTop = this.column[index].initTop
			this.column[index].columnStatus = "active"
		},
		onTouchMove(e, index) {
			this.column[index].columnStatus = "active"
			this.column[index].moveY = e.touches[0].pageY
			this.column[index].moveTop =
				this.column[index].moveY - this.column[index].startY + this.column[index].startTop
			if (this.column[index].moveTop > initTop) {
				this.column[index].moveTop = initTop
			}
			if (this.column[index].moveTop < initTop - this.wrapHeight + itemHeight) {
				this.column[index].moveTop = initTop - this.wrapHeight + itemHeight
			}
			this.column[index].initTop = this.column[index].moveTop
		},
		onTouchEnd(_e, index) {
			this.column[index].columnStatus = ""
			if (this.column[index].moveTop % itemHeight !== 0) {
				this.column[index].moveTop =
					initTop - Math.round((initTop - this.column[index].moveTop) / itemHeight) * itemHeight
			}
			this.column[index].initTop = this.column[index].moveTop
			this.column[index].activeIndex = Math.abs(Math.round((initTop - this.column[index].moveTop) / itemHeight))

			if (index === 0) {
				if (this.column[1].show) {
					this.column[1].list = this.list[this.column[0].activeIndex].children
					this.column[1].initTop = initTop
					this.column[1].moveTop = 0
					this.column[1].startTop = 0
					this.column[1].moveTop = 0
					if (this.column[2].show) {
						this.column[2].list = this.list[this.column[0].activeIndex].children[0].children
						this.column[2].initTop = initTop
						this.column[2].moveTop = 0
						this.column[2].startTop = 0
						this.column[2].moveTop = 0
					}
				}
			}
			if (index === 1) {
				if (this.column[2].show) {
					this.column[2].list = this.column[1].list[this.column[1].activeIndex].children
					this.column[2].initTop = initTop
					this.column[2].moveTop = 0
					this.column[2].startTop = 0
					this.column[2].moveTop = 0
				}
			}
		},
	},
	watch: {
		list: {
			handler(list) {
				if (list.length > 0) {
					try {
						this.column[1].show = !!list[0].children
						this.column[2].show = !!list[0].children[0].children
						return
					} catch (error) {}
				}
				this.column[1].show = false
				this.column[2].show = false
			},
			immediate: true,
			deep: true,
		},
		value: {
			immediate: true,
			deep: true,
			handler: "onFormatColumn",
		},
	},
}
</script>

<style lang="scss" scoped>
.v-select {
	display: inline-block;
	.header {
		display: flex;
		justify-content: space-between;
		align-items: center;
		font-size: 26rpx;
		div {
			padding: 20rpx 30rpx;
		}
		.title {
			white-space: nowrap;
			overflow: hidden;
			text-overflow: ellipsis;
			max-width: 50%;
			font-weight: 700;
			font-size: 28rpx;
		}
		.cancel {
			color: #999999;
			transition: opacity 0.3s;
			&:active {
				opacity: 0.7;
			}
		}
		.confirm {
			color: #3c9cff;
			transition: opacity 0.3s;
			&:active {
				opacity: 0.7;
			}
		}
	}
	.column-wrap {
		display: flex;
		justify-content: space-between;
		align-items: center;
	}
	.column {
		flex: 1;
		position: relative;
		overflow: hidden;
		height: 500rpx;
		user-select: none;
		.wrap {
			transition: transform 0.1s;
			&.active {
				transition: none;
			}
			.item {
				height: 80rpx;
				display: flex;
				justify-content: center;
				align-items: center;
				font-size: 28rpx;
			}
		}
		.frame {
			position: absolute;
			top: 50%;
			right: 16rpx;
			left: 16rpx;
			height: 80rpx;
			z-index: 2;
			transform: translateY(-50%);
			pointer-events: none;
			&::after {
				content: "";
				pointer-events: none;
				position: absolute;
				top: -50%;
				left: -50%;
				right: -50%;
				bottom: -50%;
				border: 1px solid #e5e5e5;
				transform: scale(0.5);
				box-sizing: border-box;
				border-width: 1px 0;
			}
		}
		.mask {
			position: absolute;
			background-size: 100% 220rpx;
			top: 0;
			left: 0;
			z-index: 1;
			width: 100%;
			height: 100%;
			background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.4)),
				linear-gradient(0deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.4));
			background-repeat: no-repeat;
			background-position: top, bottom;
			transform: translateZ(0);
			pointer-events: none;
		}
	}
}
</style>
